package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/gorilla/websocket"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"regexp"
	"strconv"
	"strings"
	"time"
)

type HttpConfig struct {
	Url         string
	Worker      int
	Times       int
	RequestData string
	Method      string
	Total       int
	Interval    int
	CheckScript string
	Headers     string
	Requests    []*RequestConfig
	Policy      int
}
type RequestConfigJ struct {
	Url         string
	Times       json.Number
	RequestData string
	Method      string
	Total       json.Number
	Interval    json.Number
	CheckScript string
	Headers     string
}
type RequestConfig struct {
	Url         string
	Times       int
	RequestData string
	Method      string
	Total       int
	Interval    int
	CheckScript string
	Headers     string
}

type Http struct {
	cfg       *HttpConfig
	workers   []*HttpWorker
	tm_start  time.Time
	_stop     bool
	_chstop   chan int
	_chreport chan *HttpReport
	_reports  []*HttpReport
}

func New() *Http {
	return &Http{
		_stop:     true,
		_chreport: make(chan *HttpReport, 10),
	}
}

var minSleep = 0

func (self *Http) Start() {
	self.Stop()

	self._reports = make([]*HttpReport, 0)

	self._chstop = make(chan int, 1)
	self._stop = false
	self.workers = make([]*HttpWorker, self.cfg.Worker)
	wokercfg := &WorkerConfig{
		Url:         self.cfg.Url,
		Method:      self.cfg.Method,
		Times:       self.cfg.Times,
		RequestData: self.cfg.RequestData,
		CheckScript: self.cfg.CheckScript,
		Requests:    self.cfg.Requests,
		Policy:      self.cfg.Policy,
	}
	self.tm_start = time.Now()
	max := self.cfg.Worker
	if max <= 100 {
		max = 100
	} else if max > 600 {

	}
	defaultTransport.MaxIdleConns = max
	defaultTransport.MaxIdleConnsPerHost = max
	for i := 0; i < self.cfg.Worker; i++ {
		worker := &HttpWorker{
			index: i,
		}
		if strings.TrimSpace(self.cfg.Headers) != "" {
			var h = make(map[string]string)
			json.Unmarshal([]byte(self.cfg.Headers), &h)
			worker.Head = h
		}
		worker.Config(wokercfg)
		worker.Start()
		self.workers[i] = worker
	}
	go func() {
		last := self.tm_start
		self._chreport <- &HttpReport{
			Succ:          0,
			Err:           0,
			WorkerSeconds: 0,
			Worker:        self.cfg.Worker,
			Running:       true,
			Second:        0,
			Time:          last,
			Tmsp:          last.UnixNano(),
			WsReceive:     0,
			WsSend:        0,
			WsError:       0,
			WsConn:        0,
		}
		for now := time.Now(); (!self._stop) && self.tm_start.Add(time.Second*time.Duration(self.cfg.Total)).Sub(now) > 0; now = time.Now() {
			slpsec := last.Add(time.Duration(self.cfg.Interval) * time.Second).Sub(now)
			if slpsec > 0 {
				time.Sleep(time.Duration(slpsec) * time.Nanosecond)
			}
			r := self.Report()
			last = r.Time

			self._reports = append(self._reports, r)
			self._chreport <- r

			if !r.Running {
				break
			}

		}
		self._stop = true
		self._chstop <- 1
	}()
}

type HttpReport struct {
	Succ          int
	Err           int
	WorkerSeconds float64
	Worker        int
	Running       bool
	Second        float64
	Time          time.Time
	Tmsp          int64

	WsReceive int
	WsSend    int
	WsError   int
	WsConn    int
}

func (self *Http) Report() *HttpReport {
	if self.workers == nil {
		return nil
	}
	fr := &HttpReport{
		Succ:          0,
		Err:           0,
		WorkerSeconds: 0,
		Worker:        self.cfg.Worker,
		Running:       false,
		Time:          time.Now(),
	}
	fr.Tmsp = fr.Time.UnixNano()
	for _, v := range self.workers {
		r := v.Report()
		fr.Succ += r.Success
		fr.Err += r.Error

		fr.WsReceive += r.WsReceive
		fr.WsSend += r.WsSend
		fr.WsError += r.WsError
		fr.WsConn += r.WsConn

		fr.WorkerSeconds += r.Seconds
		fr.Second = time.Now().Sub(self.tm_start).Seconds()
		if !fr.Running {
			if !r.Ended {
				fr.Running = true
			}
		}
	}
	return fr
}
func (self *Http) Stop() {
	if !self._stop {
		self._stop = true
		<-self._chstop
	}

	if self.workers != nil {

		for _, v := range self.workers {
			v.Stop()
		}
	}
}
func (self *Http) Config(c *HttpConfig) {
	self.cfg = c

}

type WorkerConfig struct {
	Url         string
	ContentType string
	Method      string
	Times       int
	RequestData string
	CheckScript string
	Policy      int
	Requests    []*RequestConfig
}
type WorkerReport struct {
	Success int
	Error   int
	Seconds float64
	Ended   bool

	WsReceive int
	WsSend    int
	WsError   int
	WsConn    int
}
type HttpWorker struct {
	IsRunning   bool
	StopS       bool
	cfg         *WorkerConfig
	requestData string
	Url         string
	Head        map[string]string
	suc         int
	err         int
	wsReceive   int
	wsSend      int
	wsError     int
	wsConn      int

	chstop  chan int
	start   time.Time
	end     time.Time
	checker *jsCaller
	index   int
}

func (self *HttpWorker) Report() *WorkerReport {
	return &WorkerReport{
		Success: self.suc,
		Error:   self.err,
		Seconds: self.end.Sub(self.start).Seconds(),
		Ended:   !self.IsRunning,

		WsReceive: self.wsReceive,
		WsSend:    self.wsSend,
		WsError:   self.wsError,
		WsConn:    self.wsConn,
	}
}
func request(reqData string, method string, url string, head map[string]string) (b []byte, re error) {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println(err)
			re = errors.New(fmt.Sprint(err))
		}
	}()
	client := &http.Client{Transport: defaultTransport}

	req, err := http.NewRequest(method, url, strings.NewReader(reqData))
	if err != nil {
		return nil, err
	}
	for k, v := range head {
		req.Header.Set(k, v)
	}
	resp, err := client.Do(req)
	defer resp.Body.Close()
	if err != nil {
		return nil, err
	}

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	return body, nil
}
func (self *HttpWorker) Request(reqData string) (b []byte, re error) {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println(err)
			re = errors.New(fmt.Sprint(err))
		}
	}()

	client := &http.Client{}

	req, err := http.NewRequest(self.cfg.Method, self.Url, strings.NewReader(reqData))
	if err != nil {
		return nil, err
	}
	for k, v := range self.Head {
		req.Header.Set(k, v)
	}

	resp, err := client.Do(req)

	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	return body, nil
}
func (self *HttpWorker) requestOnce() {
	bd, e := self.Request(self.requestData)
	if e != nil {
		self.err++
	} else {
		if self.checker != nil {
			if self.checker.CheckResult(self.requestData, string(bd)) {
				self.suc++
			} else {
				self.err++
			}
		} else {
			self.suc++
		}
	}
	self.end = time.Now()
}
func (self *HttpWorker) RequestWS(cfg *RequestConfig, repss []map[string]interface{}) map[string]string {
	url := fillParameter(cfg.Url, repss, self.index)
	reqData := fillParameter(cfg.RequestData, repss, self.index)

	c, _, err := websocket.DefaultDialer.Dial(url, nil)
	if err != nil {
		self.wsError++
		log.Fatal("dial:", err)
		return nil
	}
	self.wsConn++
	defer c.Close()

	if reqData != "" {
		go func() {
			if cfg.Times <= 0 {
				for !self.StopS {
					e := c.WriteMessage(websocket.TextMessage, []byte(reqData))
					if e != nil {
						self.wsError++
						break
					}
					self.wsSend++
				}
			}
			for i := 0; i < cfg.Times; i++ {
				if self.StopS {
					break
				}
				e := c.WriteMessage(websocket.TextMessage, []byte(reqData))
				if e != nil {
					self.wsError++
					break
				}
				self.wsSend++
			}

		}()
	}

	for {
		_, message, err := c.ReadMessage()
		if err != nil {
			log.Println("read:", err)
			self.wsError++
			return nil
		}
		self.wsReceive++
		if self.StopS {
			break
		}
		log.Printf("recv: %s", message)
	}
	return nil
}

func fillParameter(s string, repss []map[string]interface{}, workerIndex int) string {

	reg, _ := regexp.Compile("\\{\\{\\d\\..+\\}\\}")
	pars := reg.FindAllString(s, -1)
	var rs string
	rs = s
	if len(pars) == 0 {

	} else {
		for _, p := range pars {
			dotIndex := strings.Index(p, ".")
			index, _ := strconv.Atoi(p[2:dotIndex])
			params := p[dotIndex+1 : len(p)-2]
			paramsArr := strings.Split(params, ".")
			var cp interface{} = repss[index]
			for _, p := range paramsArr {
				cp = cp.(map[string]interface{})[p]
			}
			rs = strings.Replace(rs, p, fmt.Sprint(cp), 1)

		}
	}
	regIndex, _ := regexp.Compile("\\{\\{\\%0\\d+d\\}\\}")
	pars = regIndex.FindAllString(rs, -1)
	if pars != nil {
		for _, p := range pars {
			rs = strings.Replace(rs, p, fmt.Sprintf(p[2:len(p)-2], workerIndex+1), 1)
		}
	}

	return rs
}
func (self *HttpWorker) RequestHttp(cfg *RequestConfig, repss []map[string]interface{}) []byte {
	url := fillParameter(cfg.Url, repss, self.index)
	reqData := fillParameter(cfg.RequestData, repss, self.index)
	var h = make(map[string]string)
	if strings.TrimSpace(cfg.Headers) != "" {
		json.Unmarshal([]byte(cfg.Headers), &h)
	}
	for k, v := range h {
		h[k] = fillParameter(v, repss, self.index)
	}
	bd, e := request(reqData, cfg.Method, url, h)
	if e != nil {
		self.err++
	} else {
		if self.checker != nil {
			if self.checker.CheckResult(self.requestData, string(bd)) {
				self.suc++
			} else {
				self.err++
			}
		} else {
			self.suc++
		}
	}
	self.end = time.Now()
	return bd
}
func (self *HttpWorker) RequestRequest(cfg *RequestConfig, repss []map[string]interface{}) map[string]interface{} {
	if cfg.Method == "WS" {
		self.RequestWS(cfg, repss)
		return nil
	} else {
		start := time.Now()
		resp := self.RequestHttp(cfg, repss)
		respJs := make(map[string]interface{})
		if json.Unmarshal(resp, &respJs) != nil {

		}
		passMS := time.Now().Sub(start).Milliseconds()
		slp := int64(0)
		if int64(cfg.Interval)-passMS > slp {
			slp = int64(cfg.Interval) - passMS
			time.Sleep(time.Duration(slp) * time.Millisecond)
		}
		return respJs
	}
}

func (self *HttpWorker) run() {
	if self.IsRunning {
		return
	}
	self.IsRunning = true
	self.suc = 0
	self.err = 0

	self.wsReceive = 0
	self.wsSend = 0
	self.wsError = 0
	self.wsConn = 0

	self.start = time.Now()
	self.end = time.Now()
	if self.cfg.Policy == 0 {
		responses := make([]map[string]interface{}, 0)
		isFirst := true
		if self.cfg.Times <= 0 {

			for !self.StopS {
				for _, r := range self.cfg.Requests {
					result := self.RequestRequest(r, responses)
					if isFirst {
						responses = append(responses, result)
					}

				}
			}
			isFirst = false
		} else {
			for i := 0; !self.StopS && i < self.cfg.Times; i++ {
				for _, r := range self.cfg.Requests {
					result := self.RequestRequest(r, responses)
					if isFirst {
						responses = append(responses, result)
					}

				}
				isFirst = false
			}
		}

	} else {

	}
	//if self.cfg.Times > 0 {
	//	for i := 0; (!self.StopS) && i < self.cfg.Times; i++ {
	//		self.requestOnce()
	//	}
	//} else {
	//	for !self.StopS {
	//		self.requestOnce()
	//	}
	//}

	self.IsRunning = false
	self.chstop <- 1
}
func (self *HttpWorker) Start() {
	self.StopS = false
	go self.run()

}
func (self *HttpWorker) Stop() {
	if (!self.IsRunning) || self.StopS {
		return
	}
	self.StopS = true
	<-self.chstop

}
func (self *HttpWorker) Config(c *WorkerConfig) {
	self.cfg = c
	self.Url = c.Url
	self.requestData = c.RequestData

	self.chstop = make(chan int, 1)

	if strings.TrimSpace(c.CheckScript) != "" {
		sc, e := NewJsCaller(c.CheckScript)
		if e == nil {
			self.checker = sc
		}
	}
}

var hp *Http = nil

func cmd_start(d []byte) {
	hp.Start()

}

func cmd_config(d []byte) {
	c := &CMD_Config{}
	e := json.Unmarshal(d, c)
	if e != nil {
		log.Println(e)
		return
	}

	hp.Config(NewHttpConfig(&c.HttpConfigJ))

}
func cmd_stop(d []byte) {
	hp.Stop()
}
func cmd_save(d []byte) {
	//hp.Start()
}

func inithandles(w *WSServer) {
	w.Handles = make(map[int]func([]byte))
	w.Handles[CMD_START] = cmd_start
	w.Handles[CMD_CONFIG] = cmd_config
	w.Handles[CMD_STOP] = cmd_stop
	w.Handles[CMD_SAVE] = cmd_save

}

func main() {

	hp = New()
	ws := NewWSServer()
	ws.chreport = hp._chreport
	inithandles(ws)
	go ws.writeWork()
	//go get github.com/jteeuwen/go-bindata/...
	//go get github.com/elazarl/go-bindata-assetfs/...
	//go-bindata-assetfs static/...
	//h := http.FileServer(assetFS())
	h := http.FileServer(http.Dir("./static"))

	http.Handle("/", h)
	http.HandleFunc("/getdata", func(writer http.ResponseWriter, request *http.Request) {
		def := []byte(`[]`)
		d, e := ioutil.ReadFile("./data2.json")
		if e != nil || len(d) < 10 {
			d = def
		}
		writer.Write(d)

	})
	http.Handle("/ws", ws)
	e := http.ListenAndServe(":8787", nil)
	if e != nil {
		fmt.Println(e)
	}

}

var defaultTransport *http.Transport

func init() {
	defaultRoundTripper := http.DefaultTransport
	defaultTransportPointer, ok := defaultRoundTripper.(*http.Transport)
	if !ok {
		panic(fmt.Sprintf("defaultRoundTripper not an *http.Transport"))
	}

	defaultTransport = defaultTransportPointer // dereference it to get a copy of the struct that the pointer points to
	defaultTransport.MaxIdleConns = 100
	defaultTransport.MaxIdleConnsPerHost = 100
}
